原子类 CSS 的前世今生

您所在的位置:网站首页 md文件 ty 原子类 CSS 的前世今生

原子类 CSS 的前世今生

2023-03-22 06:20| 来源: 网络整理| 查看: 265

state-of-css 2021 中,满意度最高的 CSS 框架就是 Tailwind CSS ,并且近几年一直高居榜首。今天就来一探究竟吧!

可以看出Tailwind CSS 在过去两年的流行程度一直保持一个高速增长的态势,可见其原子化 CSS 的特性的确很好用。

css 思想的演进。来源:Tailwind CSS 作者 https://adamwathan.me/css-utility-classes-and-separation-of-concerns/ 阶段: 语义化的 class

最开始,最佳实践之一是“关注点分离”(separation of concerns),也就是html 只包含结构, CSS 只包含样式。HTML 的 class 应该有自己的语义,不应该把样式或者逻辑附在上面。

Hello there!

看到.text-center了吗?

text-center 是一个样式,但是放在了 html 里。所以这段代码违反了“样式与结构分离”,因为我们让样式信息渗入了我们的 HTML。

相反,推荐的方法是根据元素的内容为元素命名,并使用这些类作为CSS 来设置标记样式:

.greeting { text-align: center; }

Hello there!

一个典型例子是 CSS Zen Garden,换换 class 就给网站换上不同的风格。

工作流程

这个时候你的的工作流程:

编写一些新 UI 所需的HTML: Adam Wathan

Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.

根据内容添加一两个描述性 class:- + Adam Wathan

Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.

在 CSS/Less/Sass 中使用这些 class 来设置新标记的样式:.author-bio { background-color: white; border: 1px solid hsl(0,0%,85%); border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: hidden; > img { display: block; width: 100%; height: auto; } > div { padding: 1rem; > h2 { font-size: 1.25rem; color: rgba(0,0,0,0.8); } > p { font-size: 1rem; color: rgba(0,0,0,0.75); line-height: 1.5; } } }

最终结果:

This content is only supported in a Feishu Docs

问题

这种方法一开始看来很直观,但是仔细想想还是有一些问题。

例子中,用了 scss 提供的层级选择器写法。这是为了方便定位到元素。但写着写着,不算是又写了一遍 html 层级结构吗?

但 CSS 和 HTML 之间仍然存在非常明显的耦合,CSS 就像我们标记的一面镜子,使用嵌套的 CSS 选择器完美地反映了 HTML 结构。

所以层级选择器乃至后代选择器的写法,带来了不完整的「分离」实践:

html 不关注样式但 css 文件却关注着 html 结构第二阶段:将样式与结构分离

有什么办法能够解耦呢?

那就不得不提到大名鼎鼎的 Block Element Modifer,简称 BEM 。

块(block)、元素(element)、修饰符(modifier)的简写,由 Yandex 团队提出的一种前端 CSS 命名方法论。

采用类似 BEM 的方法, html 会变成这样:

Adam Wathan

Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.

... CSS 像这样:

.author-bio { background-color: white; border: 1px solid hsl(0,0%,85%); border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: hidden; } .author-bio__image { display: block; width: 100%; height: auto; } .author-bio__content { padding: 1rem; } .author-bio__name { font-size: 1.25rem; color: rgba(0,0,0,0.8); } .author-bio__body { font-size: 1rem; color: rgba(0,0,0,0.75); line-height: 1.5; }

This content is only supported in a Feishu Docs

那么限制不用后代选择器,通过 bem class 命名规范来尽可能做到精确定位需要装饰的元素,效果如何?

通过写 .author-boe__name 这样的类名,成功让 css 选择器不再关注 html 结构了!

某种程度上少关注了 html;

但 block-element__module 本质也是映射了一个区块的结构

样式复用

下面问题来了,图中有两种语义不同,但结构相似的模块。如何复用它们之间的样式?

首先,之前定义的 .author-bio 系列 class,肯定不能直接用在这个文章预览的模块上,因为不符合语义。

选项 1:copy-paste

不复用。改改类名,copy 出一整份 .article-preview 系列的样式。

不符合 DRY(Don't Repeat Yourself)原则 。如果以后卡片的风格需要调整,会修改多处。比如下方内容的 padding,要修改两处。.article-preview { background-color: white; border: 1px solid hsl(0,0%,85%); border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: hidden; } .article-preview__image { display: block; width: 100%; height: auto; } .article-preview__content { padding: 1rem; } .article-preview__title { font-size: 1.25rem; color: rgba(0,0,0,0.8); } .article-preview__body { font-size: 1rem; color: rgba(0,0,0,0.75); line-height: 1.5; } 选项 2: @extend

也不用直接 copy,一般 css 预处理器都有诸如 @extend 这样的语法,可以让一个选择器内的样式继承另一个选择器。

.article-preview { @extend .author-bio; } .article-preview__image { @extend .author-bio__image; } .article-preview__content { @extend .author-bio__content; } .article-preview__title { @extend .author-bio__name; } .article-preview__body { @extend .author-bio__body; }

This content is only supported in a Feishu Docs

使用@extend一般不建议 使用,比如在毫无语义关系的两者间建立依赖、样式优先级混乱等等问题。

选项 3:抽象出一套只关心样式的 class

从“语义”的角度来看,我们.author-bio和.article-preview组件没有任何共同之处。

但从设计的角度来看,它们有很多共同点。

因此,我们可以抽象出一套只关心样式的 class,来进行样式复用。

我们称它为.media-card。

这是 CSS:

Adam Wathan

Adam is a rad dude who likes TDD, Active Record, and garlic bread with cheese. He also hosts a decent podcast and has never had a really great haircut.

Stubbing Eloquent Relations for Faster Tests

In this quick blog post and screencast, I share a trick I use to speed up tests that use Eloquent relationships but don't really depend on database functionality.

.media-card { background-color: white; border: 1px solid hsl(0,0%,85%); border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); overflow: hidden; } .media-card__image { display: block; width: 100%; height: auto; } .media-card__content { padding: 1rem; } .media-card__title { font-size: 1.25rem; color: rgba(0,0,0,0.8); } .media-card__body { font-size: 1rem; color: rgba(0,0,0,0.75); line-height: 1.5; }

这种方法的确消除了我们 CSS 中的重复,但是说好的「样式与结构之关注点分离」原则呢?

如果我们想改变作者卡片而不改变文章卡片,该怎么办呢?

比如要给文章卡片换一套展示风格 media-card2,需要去给 html 文件加上对应的 class 。

小结

总结一下,如果是原来的语义化类名写法,如果我们想复用样式:

写必要的 html 结构挂上一套设计好的语义化的类名打开 css 文件,走硬复制或 @extend 之类的路

如果是的非语义化命名 .media-card 写法:

写必要的 html 结构挂上 .media-card 类名

这样,不仅减少了类名的思考,而且我们只改一处代码。

相比于第一种「关注点分离」的语义化写法,是不是更友好了?

重新思考关注点分离 separation of concerns

那么「关注点分离」到底是为了什么?语义化的类名规范,就是对这一原则的全部诠释吗?

我们得重新思考 html 与 css 之间的连接关系,也对应了不同 class 的设计。

大致有两种角度:

关注点分离 : css 依赖于 html(阶段一)

这种角度下,class 只需关心 html 内容的语义即可。根据内容命名 class,比如 .author-bio

这种结构下,html 很容易被重新更新 class 样式,而 css 很难被复用。

典型例子就是之前提到过的 http://www.csszengarden.com/

混合关注 : html 依赖于 css(阶段二的选项三)

类名根据其想表达的样式设计模式来设计,如 .media-card (多媒体卡片);html 则要参考当前的设计系统,挑选合适的样式挂载到内容上。

这种结构下,css 复用是很自然的事;而 html 要改样式只得改代码。

典型例子就是 Bootstrap 这样的样式框架。

所以耦合是必然存在的(class html),两种角度都做到了一定程度的「关注点分离」,在实现这一原则上没有优劣之分。

我们再考虑一下,对你的网站来说,是方便切换样式重要;还是复用样式代码重要?

最后,html 语义前端架构 这篇文章深刻影响了 tailwind 作者,并彻底转向基于「复用样式」的 class 实践。简单摘录一点内容:

html/jsx 的元素标签、内容本身已经反映其内容的语义;对于代码维护者而言,下面的 .news class 能带来的有效信息极其有限;其所承载的样式也基本无法被复用到其他场景。export default function News({newsList}) { return // class news 其实是语义重复 News {newsList.map(n => {n})} } Tailwind CSS 优势

https://tailwindcss.com/

统一设计规范

首先,设计同学在出设计稿的时候都是有自己的规范的,例如边距只会是4px的倍数,字体只会从常规大小里面去取:

颜色,有一套标准的模版:

而 Tailwind 支持配置自己的设计规范。

在掌握了封装 CSS class 这种技术之后,我们如鱼得水。而且就算后续设计修改了某一种字体的大小,或者某一种颜色的值时,我们都能够很快的应对,甚至只需要修改一行代码。

module.exports = { content: ['./src/**/*.{html,js}'], theme: { colors: { 'blue': '#1fb6ff', 'purple': '#7e5bef', 'pink': '#ff49db', 'orange': '#ff7849', 'green': '#13ce66', 'yellow': '#ffc82c', 'gray-dark': '#273444', 'gray': '#8492a6', 'gray-light': '#d3dce6', }, fontFamily: { sans: ['Graphik', 'sans-serif'], serif: ['Merriweather', 'serif'], }, extend: { spacing: { '8xl': '96rem', '9xl': '128rem', }, borderRadius: { '4xl': '2rem', } } }, }

除此之外,Tailwind还有一个极大的优势,就是我们可以方便的使用config的方式,来快速扩展和修改基础样式,例如我想新增一个叫做flex-center的样式,这个样式具有flex,align-items: center和justify-content: center的属性,可以直接这样写:

// tailwind.config.js const plugin = require('tailwindcss/plugin'); /** @type { import('@types/tailwindcss/tailwind-config').TailwindConfig } */ module.exports = { plugins: [ plugin(function ({ addUtilities }) { const newUtilities = { '.flex-center': { display: 'flex', alignItems: 'center', justifyContent: 'center', }, }; addUtilities(newUtilities); }), ], }; 再也不用花时间想命名了

命名可以算是开发中最难的事情之一,尤其是在组件化开发已经深入人心的今天,你其实完全没必要给你的 div 起一个有意义的名字。使用这个组件的页面并不会关心你组件的顶部叫 header,底部叫 footer(除非你是些基础组件需要给外界复用),你只需要把样式放上去就好了。

你起过多少 wrapper、container、content、box、section、fragment 这种没意义的 className?

同样一段代码,不用起名字可以少掉不少头发

减少代码

所有使用 CSS(包括 CSS modules)的解决方案其实都有一个问题,就是不好删代码:你很难确定这段样式是不是真的没用了,直到出线上事故为止。使用 tailwind css 你可以让样式到死都跟着组件走,组件删了样式也就去掉了,几乎零成本的降低了冗余代码的可能性。

常见问题Q:类名太长了

解决方法 @apply Functions & Directives - Tailwind CSS

Q:和行内 CSS 有什么区别?支持伪类 支持响应式 // 屏幕大于 1024px 宽时设更宽的宽度 规范你的设计体系

使用 tailwind 带来的整套设计系统,能让你的网站的整体布局、颜色、字体风格更加和谐和一致。让开发者随意增添 css 规则,哪怕一些知名网站也会难以管理其使用的字体大小规范。

GitLab: 402 text colors, 239 background colors, 59 font sizesBuffer: 124 text colors, 86 background colors, 54 font sizesHelpScout: 198 text colors, 133 background colors, 67 font sizesGumroad: 91 text colors, 28 background colors, 48 font sizesStripe: 189 text colors, 90 background colors, 35 font sizesGitHub: 163 text colors, 147 background colors, 56 font sizesConvertKit: 128 text colors, 124 background colors, 70 font sizes、Q:可以自己写 class 吗?

当然可以。

有些场景我们确实不希望在 html/jsx 里分离出样式相关的内容;此时,我们依然可以抽象出传统语义化的 class,并在 css 内 apply tailwind 的规则。这样依然可以享受到 tailwindcss 的绝大多数好处。

Click me Button .btn { @apply py-2 px-4 font-semibold rounded-lg shadow-md; } .btn-green { @apply text-white bg-green-500 hover:bg-green-700; }

但在基于组件的 web 框架中,一般我们会这样抽象 button。有点类似 styledComponent 的做法,但还可以完整地使用组件能力。

function ConfirmButton() { // logic return Click me } Q:造成新的记忆负担?

这个问题就仁者见仁智者见智了,在 Vue 的 template 语法中也经常出现此类问题,很多人会对一些命名上的约定,特别是自己不太喜欢的约定天然排斥,这也无可厚非。

解决方法 IntelliSense- Tailwind CSS,通过插件降低学习成本 Tailwind CSS IntelliSense 、Tailwind CSS Transpiler。

插件提供了预览提示功能,减少记忆负担。

Figma 也有对应的 Tailwind 插件。

随便找一个区域,选中,插件,Figma to Code, Tailwind 2。

Q:初期很爽,但是后期维护困难?团队有统一的规范,更易于维护。调试,可轻松使用 chrome ``devtools,还是可以一眼望到底的,而且没有以前各种 class 存在属性重复覆盖,造成调试困难,从下图可看出 tailwindcss 调试一目了然适用场景

总体上,如果是基础组件开发或者是“设计和开发结合在一起”的开发场景比较适合Tailwind这种原子化class去开发。Tailwind提供的是设计规范,相当于理念层面的素材。比如一个button组件,如果完全自己设计样式,首先需要对button进行拆解,它的边框、字体、阴影、圆角等等,你要从框架里面把这些元素挑出来进行组合(堆积木的感觉)。

所以它更适合有统一设计的团队,因为对于这些团队而言,写好设计规范,接下来就是拼凑,如果设计师直接通过组合得出效果,对研发人员来说也节省了时间。

以乐小活为例,目前我们的项目大部分是用 ByDeign 组件默认封装的样式,只有当默认样式与设计稿不符合时,需要自己覆盖。我认为Tailwind暂不适用目前 PC 项目的原因有以下几点:

目前项目处于不断提需求、功能开发阶段,由于迭代速度快,开箱即用的组件和配套的组件化样式更满足于当前情况,前端使用的时候不需要关注太多样式细节,而Tailwind有一定学习成本,且团队所有人都对 className 达成一定共识时,才能更好发挥它的作用(比如当设计稿变更时,任意人员都可以更改原子类映射的值,降低协作成本)。目前项目样式方面我认为一个比较明显的痛点,是需要写较多样式覆盖的代码,甚至有时为了使样式生效,需要“挖”比较深,但若仅仅将组件默认样式换成组件默认样式+Tailwind并不能解决样式覆盖问题Tailwind样式的单位是rem,rem 是相对于根元素 html 的 font-size 来计算的,所以Tailwind的响应式做的很好,但是目前项目对响应式的要求没那么高,且设计稿一般是给定固定宽高,在这种需求下,用Tailwind设置宽高还需自己换算一下单位,反而有些麻烦

而 Tailwind 比较适合项目工程较大(页面多)、有标准化设计(色卡、字体、sizing、layout)、重复率高、页面结构较简单的项目,目前看来官网、C端的业务目前来看比较符合这些特点,而 SaaS 、后台管理则不适用。The Beauty of CSS Design而 Tailwind 比较适合项目工程较大(页面多)、有标准化设计(色卡、字体、sizing、layout)、重复率高、页面结构较简单的项目,目前看来官网、C端的业务目前来看比较符合这些特点,而 SaaS 、后台管理则不适用。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3